前言

  如今,大半个互联网都建立在 TCP 协议之上,我们使用的 HTTP 协议、消息队列、存储、缓存,都需要用到 TCP 协议——这是因为 TCP 协议提供了可靠性。简单来说,可靠性就是让数据无损送达。但若是考虑到成本,就会变得非常复杂——因为还需要尽可能地提升吞吐量、降低延迟、减少丢包率。

本章节待解决问题

Q1: TCP 协议是如何恢复数据的顺序的?
Q2: 拆包和粘包的作用是什么?

1. TCP的拆包和粘包

  TCP 是一个传输层协议。TCP 发送数据的时候,往往不会将数据一次性发送。

  TCP 为什么不一次发送完所有的数据?比如我们要传一个大小为10M的文件,对于应用层而言,就是一次传送完成的。而传输层的协议为什么不选择将这个文件一次发送完呢? 这里有很多原因,比如为了稳定性,一次发送的数据越多,出错的概率越大。再比如说为了效率,网络中有时候存在着并行的路径,拆分数据包就能更好地利用这些并行的路径。再有,比如发送和接收数据的时候,都存在着缓冲区。 总之,方方面面的原因:在传输层封包不能太大。这种限制,往往是以缓冲区大小为单位的。也就是 TCP 协议,会将数据拆分成不超过缓冲区大小的一个个部分。每个部分有一个独特的名词,叫作 TCP 段(TCP Segment)。

2. TCP Segment

TCP 协议就是依靠每一个 TCP 段工作的,所以你每认识一个 TCP 的能力,几乎都会找到在 TCP Segment 中与之对应的字段。下图是一个 TCP 段的格式。

alt

  1. Source Port/Destination Port 描述的是发送端口号和目标端口号,代表发送数据的应用程序和接收数据的应用程序。比如 80 往往代表 HTTP 服务,22 往往是 SSH 服务……

  2. Sequence Number 和 Achnowledgment Number 是保证可靠性的两个关键。具体见下文的讨论。

  3. Data Offset 是一个偏移量。这个量存在的原因是 TCP Header 部分的长度是可变的,因此需要一个数值来描述数据从哪个字节开始。

  4. Reserved 是很多协议设计会保留的一个区域,用于日后扩展能力。

  5. URG/ACK/PSH/RST/SYN/FIN 是几个标志位,用于描述 TCP 段的行为。也就是一个 TCP 封包到底是做什么用的?

      URG 代表这是一个紧急数据,比如远程操作的时候,用户按下了 Ctrl+C,要求终止程序,这种请求需要紧急处理.ACK 代表响应,所有的消息都必须有 ACK,这是 TCP 协议确保稳定性的一环。PSH 代表数据推送,也就是在传输数据的意思;SYN 同步请求,也就是申请握手;FIN 终止请求,也就是挥手。

  6. Window 也是 TCP 保证稳定性并进行流量控制的工具。

  7. Checksum 是校验和,用于校验 TCP 段有没有损坏。

  8. Urgent Pointer 指向最后一个紧急数据的序号(Sequence Number)。它存在的原因是:有时候紧急数据是连续的很多个段,所以需要提前告诉接收方进行准备。

  9. Options 中存储了一些可选字段,比如接下来我们要讨论的 MSS(Maximun Segment Size)。

  10. Padding 存在的意义是因为 Options 的长度不固定,需要 Pading 进行对齐。

3. Sequence Number和Acknowledgement Number

  在 TCP 协议的设计当中,数据被拆分成很多个部分,部分增加了协议头。合并成为一个 TCP 段,进行传输。这个过程,我们俗称拆包。

  发送数据的时候,为每一个 TCP 段分配一个自增的 Sequence Number。接收数据的时候,虽然得到的是乱序的 TCP 段,但是可以通过 Seq 进行排序。 对于任何一个接收方,如果知道了发送者发送某个 TCP 段时,已经发送了多少字节的数据,那么就可以确定发送者发送数据的顺序。 但是这里有一个问题。如果接收方也向发送者发送了数据请求(或者说双方在对话),接收方就不知道发送者发送的数据到底对应哪一条自己发送的数据了。

  举个例子:下面 A 和 B 的对话中,我们可以确定他们彼此之间接收数据的顺序。但是无法确定数据之间的关联关系,所以只有 Sequence Number 是不够的。

  A:今天天气好吗?
  A:今天你开心吗?
  B:开心
  B:天气不好
  人类很容易理解这几句话的顺序,但是对于机器来说就需要特别的标注。因此我们还需要另一个数据,就是每个 TCP 段发送时,发送方已经接收了多少数据。用 Acknowledgement Number 表示,下面简写为 ACK。

  无论 Seq 还是 ACK,都是针对“对方”而言的。是对方发送的数据和对方接收到的数据。

4. MSS(Maximun Segment Size)

  MSS控制了TCP段的大小,它是一个协商字段(Negotiate)。协议是双方都要遵循的标准,因此配置往往不能由单方决定,需要双方协商。

  如果MSS太大,就会带来一些影响。首先对方可能会拒绝,作为服务的提供方,你可能不会愿意接收太大的 TCP 段。因为大的 TCP 段,会降低性能,比如内存使用的性能。 还有就是资源的占用。 一个用户占用服务器太多的资源,意味着其他的用户就需要等待或者降低他们的服务质量。 另一方面,支持 TCP 协议工作的 IP 协议,工作效率会下降。TCP 协议不肯拆包,IP 协议就需要拆出大量的包。那么 IP 协议为什么需要拆包呢?这是因为在网络中,每次能够传输的数据不可能太大,这受限于具体的网络传输设备,也就是物理特性。但是 IP 协议,拆分太多的封包并没有意义。因为可能会导致属于同个 TCP 段的封包被不同的网络路线传输,这会加大延迟。同时,拆包,还需要消耗硬件和计算资源。

  如果MSS太小,会浪费传输资源(降低吞吐量)。因为数据被拆分之后,每一份数据都要增加一个头部。如果 MSS 太小,那头部的数据占比会上升,这让吞吐量成为一个灾难。

  所以在使用的过程当中,MSS 的配置,往往都是一个折中的方案。而根据 Unix 的哲学,不要去猜想什么样的方案是最合理的,而是要尝试去用实验证明它,一切都要用实验依据说话。

小结

TCP 协议是如何恢复数据的顺序的,TCP 拆包和粘包的作用是什么?

  TCP 拆包的作用是将任务拆分处理,降低整体任务出错的概率,以及减小底层网络处理的压力。拆包过程需要保证数据经过网络的传输,又能恢复到原始的顺序。这中间,需要数学提供保证顺序的理论依据。TCP 利用(发送字节数、接收字节数)的唯一性来确定封包之间的顺序关系。具体的算法。粘包是为了防止数据量过小,导致大量的传输,而将多个 TCP 段合并成一个发送。